-- [AuXnet] v2
-- networking framework
-- server side
-- net.process() -- run server networking code , should be used very frequently
-- net.set(name,value) -- set a value in the server datastream [ ! cvars must be accessed by their CVAR name, not their variable name! ]
-- net.get(name) -- retrieve a value from the server datastream
-- net.broadcast(data) -- broadcast a string to all clients
-- net.pushstream(stream) -- encode and broadcast a datastream
-- net.loadstream(stream) -- decode a datastream
-- net.event(name,callback) -- callback will be executed if a client calls the event name -- hands parameters: ( user: the client id of who triggered the event )
-- net.action(name,callback) -- event, except parameters can be sent hands parameters: ( data : the leftover string from the message , user: who triggered the action ) -- can also call an action if callback is a value and not a fucntion
-- net.trigger(event) -- trigger an event on all clients
-- net.tell(data,cid) -- send data to a specific client only
-- net.ip(client) returns the ip address of a connected client
-- net.raw(callback) -- add a function to be called whenever the server receives a message. hands paramaters: ( data: string that was received )
-- net.disconnect(callback) -- called with client object whenever a player disconnects or loses connection hands parameters: ( ply: client object thta disconnected )
-- net.createCVARlist(client,variables) -- generate a list of cvars based on a client id and a table of variables eg: {this="that",hello="hello world"}
-- net.CVARexists(cvar[,client]) -- returns whether a cvar exists or not  < input the cvar, not the original variable >
-- net.translateCVAR(client,cvar) -- returns the value of a cvar
-- net.translateCVARname(client,variablename) -- returns the predicted name of a new cvar
-- net.getCVARbind(cvar) -- returns what client id this cvar is bound to
-- net.getCVARorig(client,cvar) -- returns the variable name before it was a cvar
-- net.setCVAR(client,variable,value) -- encodes a variable into a cvar and adds it to the list
-- net.clientpermission(client,variable) -- returns whether the client has permission to change this variable in the datastream
-- net.addclientpermission(client,variables) -- adds a variable name that a client has access to
--[[	use a ^ at the start of a variable name to indicate that it is later encoded into a cvar	EG: "^text" will give a client access to the cvar that it created using the variable "text".	--]]

socket=require "socket"
net={ }
stream={ }
players={ }
uid={ }
evt={ }
c_prm={}
cvar={ }
rebind= { }
action={ }
disconnect=function(v) return end
raw=nil
function net.start(port)
	listen=socket.tcp()
	listen:bind('*',port)
	listen:listen(5)
	listen:settimeout(0)
end
function net.process()
	c=listen:accept()
	if c then
		--print("accepted new connection")
		c:settimeout(0)
		players[#players+1]=c
		--print(c,#players)
		uid[c]=#players
		net.createCVARlist(c,{text="default text!"})
	end
	for k,v in pairs(players) do
		ms="sadfasdf"
		repeat
			ms,err=v:receive('*l')
			if ms then
				if raw then raw(ms) end
				--print("received "..ms)
				if ms:sub(1,1)==":" then
					ev=ms:gsub(":","")
					if evt[ev] then evt[ev](v) end
				elseif ms:sub(1,1)=="<" then
					cdata=net.loadstream(ms:gsub("<",""))
					--print("received a stream")
					for i,x in pairs(cdata) do
						--print("processing stream variable "..i.." with value "..x)
						if net.translateCVAR(v,i) then
							--print("variable is an existing cvar, overriding")
							if net.clientpermission(v,net.translateCVARname(v,i)) then
								--print("client has permissions.")
								stream[net.translateCVARname(v,i)]=x
							else
								--print("client does not have permission to a cvar..?")
							end
						else
							if net.clientpermission(v,i) then
								--print("variable not overridden by CVAR hooks, setting global server variable.")
								stream[i]=x
							else
								--print("client does not have permission to global variable.")
							end
						end
					end
					net.pushstream(stream)
				elseif ms:sub(1,1)=="-" then
					id=ms:match("^-(.+)-.+")
					if action[id] then action[id](ms:match(".*-.*-(.+)"),v)
				end
				end
			end			
		until not ms
		if err then
			if err~="timeout" then
				disconnect(v)
				table.remove(players,k)
			end
		end
	end
	socket.sleep(0.01)
end
function net.action(name,callback)
	if type(callback)=="function" then
		action[name]=callback
	else
		--print("calling action "..name.. " with value "..callback)
		net.broadcast("-"..name.."-"..callback)
	end
end
function net.ip(client)
	return client:getpeername()
end
function net.pushstream(data)
	packed="_=nil;"
	sendto={ }
	cvars={ }
	for k,v in pairs(data) do
		if net.CVARexists(k) then
			if not sendto[net.getCVARbind(k)] then sendto[net.getCVARbind(k)]={ } end
			sendto[net.getCVARbind(k)][#sendto[net.getCVARbind(k)]+1]=packed..(net.getCVARorig(net.getCVARbind(k),k)).."="..v..";"			
			--print("pushed 1 cvar onto stream stack")
		else
			--print("pushed global variable onto stream stack")
			packed=packed..k.."="..v..";"
		end		
	end
	c=0
	for i,z in pairs(sendto) do
		for t,m in pairs(z) do
			net.tell("<"..m,i)
			c=c+1
		end
	end
	--print("cycled through "..c.." cvars")
	if packed~="" then
		net.broadcast("<"..packed)
	else
		--print("corrupted pushstream(), see sendto/packed parsing algorithm.")
	end
end
function net.raw(callback)
	raw=callback
end
function net.disconnect(callback)
	disconnect=callback
end
function net.translateCVAR(client,variable)
	return cvar[client][variable..tostring(client)]
end
function net.CVARexists(variable,client)
	if client then
		if cvar[client][variable..tostring(client)] then
			return true
		else
			return false
		end
	else
		for k,v in pairs(cvar) do
			for e,x in pairs(v) do
				if e==variable then
					return true
				end
			end
		end
		return false
	end
end
function net.getCVARbind(variable)
	pstr=variable:match("%S+(tcp.client.:%s%S+)")
	for k,v in pairs(players) do
		if tostring(v)==pstr then
			return v
		end
	end
end
function net.translateCVARname(client,variable)
	return variable..tostring(client)
end
function net.getCVARorig(client,variable)
	--print("original of "..variable.." is "..variable:gsub(tostring(client),""))
	return variable:gsub(tostring(client),"")
end
function net.createCVARlist(client,variables)
	cvar[client]={ }
	for i,x in pairs(variables) do
		cvar[client][i..tostring(client)]=x
		--print(i..tostring(client))
		c_prm[#c_prm+1]=i..tostring(client)
		--print("added client permission to variable "..c_prm[#c_prm])
	end
end
function net.setCVAR(client,variable,value)
	--print("cvar set: "..variable..tostring(client).. " is now set to "..value)
	cvar[client][variable..tostring(client)]=value
end
function net.clientpermission(client,perm)
	f=false
	for k,v in pairs(c_prm) do
		--print(k,v,"searching: "..perm)
		if v==perm then f=true end
	end
	return f
end
function net.setclientpermission(client,perms)
	for k,v in pairs(perms) do
		if v:sub(1,1)=="^" then
			c_prm[#c_prm+1]=v:gsub("^","")..tostring(client)
		else
			c_prm[#c_prm+1]=v
		end
	end
end
function net.loadstream(data)
	ndat={ }
	repeat
		k,v=data:match("^([%w%s_]+)=([%w_%!%@%#%$%%%^%&%*%s]+);")
		if not k or not v then
			--print("attempted to decode broken format.")
			break
		end
		ndat[k]=v
		--print("searching for "..k.."="..v..";".." in "..data)
		data=data:gsub(k.."="..v..";","")
		--print("data is now "..data)
	until data==""
	return ndat
end
function net.broadcast(data,mid)
	for k,v in pairs(players) do
		v:send(data.."\n")
	end
end
function net.trigger(event)
	net.broadcast(":"..event)
end
function net.event(name,callback)
	evt[name]=callback
end
function net.tell(data,uid)
	for k,v in pairs(players) do
		if v==uid then
			v:send(data.."\n")
		end
	end
end
function net.set(name,value)
	stream[name]=value
	pushstream(stream)
end
function net.get(name)
	return stream[name]
end